# File Name： dt
# Description :
# Author : ‘Xuegen Wang’
# create_date： 2025/3/6
# Change Activity: 修改任务更新代码
import os
import copy
import json
import time
from datetime import datetime, timedelta
import traci
import random
from loguru import logger
import subprocess
import shutil
import glob
import traci.constants as tc
import traceback

import netxml
import routepy


ADDED_RANGES = {}
ADDED_JUNCTIONS = set()
VESSEL2LANE = {}
TRUCK_EXISTS_SET = set()   # 存储、维护路网中所有车辆，包括长期停在停车区中的
TRUCK_SUBSCRIBED_SET = set()  #存储、维护路网中所有作业车辆，暂时不用。traci.vehicle.getIDList() = TRUCK_SUBSCRIBED_SET + TRUCK_UNSUBSCRIBED_SET
TRUCK_UNSUBSCRIBED_SET = set()  #存储、维护路网中不输出坐标等信息给Digital Twin(DT)、无作业任务的车辆，可以直接调用setRoute()
VEHICLE_TO_STOP_COORD = {}  #用于存储APPEND_STOP_LIST中随机选择的停车点信息
ADDED_UPDATED_VEHICLE_ID_EDGE = {}


# def generate_sumocfg(net_file, route_file):
#     str1 ='''<?xml version="1.0" ?>
# <configuration>
#     <input>
#         <net-file value="test.net.xml"/>
#         <route-files value="test.rou.xml"/>
#         <gui-settings-file value="viewsettings.xml"/>
#     </input>
# </configuration>
#     '''
#     str2=str1.replace("test.net.xml", net_file)
#     str3 = str2.replace("test.rou.xml", route_file)
#     with open("dt.sumocfg", "w", encoding="UTF-8") as config:
#         print(str3, file=config)


def append_rest_stop_to_vehicle(ROUTE_FILE, vehicle_id, configs):
    """
    主要用于无vessel_id等属性的车辆从现有位置驶向休息区
    :param ROUTE_FILE: 
    :param vehicle_id: 
    :return: 
    """""
    global APPEND_STOP_LIST, VEHICLE_TO_STOP_COORD, TRUCK_SUBSCRIBED_SET,TRUCK_UNSUBSCRIBED_SET, ADDED_UPDATED_VEHICLE_ID_EDGE
    if vehicle_id not in ADDED_UPDATED_VEHICLE_ID_EDGE:
        current_edge = routepy.get_edge_from_rouxml(ROUTE_FILE, vehicle_id)
    elif vehicle_id in ADDED_UPDATED_VEHICLE_ID_EDGE:
        current_edge = ADDED_UPDATED_VEHICLE_ID_EDGE[vehicle_id]


    random_stop_temp = random.choice(APPEND_STOP_LIST)
    APPEND_STOP_LIST.remove(random_stop_temp)
    VEHICLE_TO_STOP_COORD[vehicle_id] = random_stop_temp
    rest_stop_edge = random_stop_temp['lane'][:-2]
    lane_index = 0
    lane_pos = 5
    rest_route = traci.simulation.findRoute(current_edge, rest_stop_edge)
    traci.vehicle.setRoute(vehicle_id, rest_route.edges)
    traci.vehicle.setStop(vehID=vehicle_id, edgeID=rest_stop_edge, laneIndex=lane_index, pos=lane_pos, duration=configs.TRUCK_STOP_TIME)


def vehicle_task_check(vehicle_id,task_info,simulation_time, configs):
    """
    主要用于清理无效（无vessel_id, 坐标为空）集卡作业数据：通过发送"stop_exist": 0告知redis中task_data管理者删除集卡vehicle_id的无效作业数据
    """""
    global VESSEL2LANE, ROUTE_FILENAME
    check_vessel_id = task_info["vessel_id"]
    if check_vessel_id not in VESSEL2LANE.keys():

        if vehicle_id in TRUCK_UNSUBSCRIBED_SET:
            TRUCK_UNSUBSCRIBED_SET.remove(vehicle_id)
        TRUCK_UNSUBSCRIBED_SET.add(vehicle_id)

        if vehicle_id not in traci.vehicle.getLoadedIDList():
            current_pos = traci.vehicle.getPosition(vehicle_id)
            angle = traci.vehicle.getAngle(vehicle_id)
        else:
            current_pos = traci.vehicle.getPosition(vehicle_id)
            angle = traci.vehicle.getAngle(vehicle_id)
        vehicle_tk_data = {
            "truck_id": vehicle_id,
            "task_id": task_info["task_id"],
            "simulation_start": configs.SIMULATION_START,
            "simulation_time": simulation_time,
            "current_pos": current_pos,
            "angle": (angle-90) % 360,
            "stop_exist": 0
        }

        append_rest_stop_to_vehicle(ROUTE_FILENAME, vehicle_id, configs)
        # time_to_flag3 = time.time() - start_time
        # logger.info(f"时间到标志3: {time_to_flag3:.4f} 秒")
        redis_task_data_manager(vehicle_tk_data, configs, vehicle_id)


        return False
    elif len(task_info["stop_coord"]) >= 1:
        return True


def redis_task_data_manager(task_data, configs, vehicle_id=None):
    if task_data:
        if vehicle_id is not None:
            # configs.redis_conn.hset('tk_data', vehicle_id, json.dumps(task_data))
            logger.warning(f"集卡{vehicle_id}将驶向停车区，因为其vessel_id不存在！")
        if vehicle_id is None:
            # configs.redis_conn.hset("tk_data", mapping=task_data)
            logger.warning(f"标志3：向孪生发送{len(task_data)}辆车坐标信息。")


def unsubscribe_all_trucks():
    for vehicle_id in TRUCK_SUBSCRIBED_SET:
        traci.vehicle.unsubscribe(vehicle_id)
    TRUCK_SUBSCRIBED_SET.clear()  # 清空订阅集合


def set_vehicle_route_and_stop(vehicle_id, task_info, configs):
    global VESSEL2LANE, APPEND_STOP_LIST, ROUTE_FILENAME, VEHICLE_TO_STOP_COORD,TRUCK_UNSUBSCRIBED_SET, TRUCK_SUBSCRIBED_SET, ADDED_UPDATED_VEHICLE_ID_EDGE
    try:
        if vehicle_id not in ADDED_UPDATED_VEHICLE_ID_EDGE:
            current_edge = routepy.get_edge_from_rouxml(ROUTE_FILENAME, vehicle_id)  # 从rou.xml中获取边信息
        elif vehicle_id in ADDED_UPDATED_VEHICLE_ID_EDGE:
            current_edge = ADDED_UPDATED_VEHICLE_ID_EDGE[vehicle_id]

        # if current_edge is None:
        #还要处理current_edge为空的情况
        else:
            current_edge = traci.vehicle.getRoadID(vehicle_id)

        stop_coords = task_info["stop_coord"]
        route_edges = []
        temp_stop = random.choice(APPEND_STOP_LIST)
        APPEND_STOP_LIST.remove(temp_stop)
        VEHICLE_TO_STOP_COORD[vehicle_id] = temp_stop
        stop_coords.append(temp_stop)
        # 设置路由
        for stop in stop_coords:
            lane_type = stop.get("type")
            if lane_type == "yard":
                stop_edge = stop.get("lane")[:-2]
            elif lane_type == "qc":
                sumo_stop_lane_index = int(stop.get("lane")[-1])
                vessel_id = task_info["vessel_id"]
                stop_lane = VESSEL2LANE[vessel_id]['dtlane2sumolane'][sumo_stop_lane_index]
                stop_edge = stop_lane[:-2]
            elif lane_type == "rest":
                stop_edge = stop.get("lane")[:-2]
            route = traci.simulation.findRoute(current_edge, stop_edge)

            if route.edges:
                if len(route_edges) == 0:
                    route_edges.extend(list(route.edges))
                else:
                    route_edges.pop()
                    route_edges.extend(list(route.edges))
                current_edge = route.edges[-1]
            else:
                logger.info(f"集卡{vehicle_id}没有找到去 {stop_edge} 的路径，请检查路网是否正确。")
                logger.error(traceback.format_exc())
                return False
        tuple_route_edges = tuple(route_edges)

        traci.vehicle.setRoute(vehicle_id, tuple_route_edges)

        # 设置停车点
        direction = VESSEL2LANE[vessel_id]["direction"]
        for stop in stop_coords:
            stop_x, stop_y = stop["coord"]
            lane_type = stop.get("type")
            lane = stop["lane"]
            worktime = float(stop["worktime"])
            lane_index = int(lane[-1])
            lane_length = 0

            if lane_type == "qc":
                lane_temp = VESSEL2LANE[vessel_id]["dtlane2sumolane"][lane_index]
                stop_edge = lane_temp[:-2]
                lane_id = int(lane_temp[-1])
                lane_length = traci.lane.getLength(lane_temp)
                if direction == "right":
                    lane_pos = abs(stop_x - VESSEL2LANE[vessel_id]["start_x"])
                elif direction == "left":
                    lane_pos = abs(VESSEL2LANE[vessel_id]["start_x"] - stop_x)

            elif lane_type == "yard":
                stop_edge = lane[:-2]
                lane_id = lane_index
                lane_length = traci.lane.getLength(lane)
                if lane[0] == "-": # 负向
                    stop_edge = stop_edge[1:]
                    junction_id_from = stop_edge.split("T")[1]
                    x_from, y_from = traci.junction.getPosition(junction_id_from)
                    lane_pos = abs(x_from - stop_x)
                else:
                    junction_id_from = stop_edge.split("T")[0]
                    x_from, y_from = traci.junction.getPosition(junction_id_from)
                    lane_pos = abs(stop_x - x_from)

            elif lane_type == "rest":
                stop_edge = lane[:-2]
                lane_id = lane_index
                lane_length = traci.lane.getLength(lane)
                if lane[0] == "-":
                    stop_edge_temp = stop_edge[1:]
                    junction_id_from = stop_edge_temp.split("T")[0]
                    junction_id_to = stop_edge.split("T")[1]
                    x_from, y_from = traci.junction.getPosition(junction_id_from)
                    x_to, y_to = traci.junction.getPosition(junction_id_to)
                    if abs(y_from - y_to) < 1 and abs(x_from - x_to) > 1:
                        lane_pos = abs(stop_x - x_from)
                    elif abs(x_from - x_to) < 1 and abs(y_from - y_to) > 1:
                        lane_pos = abs(stop_y - y_from)
                else:
                    junction_id_from = stop_edge.split("T")[0]
                    junction_id_to = stop_edge.split("T")[1]
                    x_from, y_from = traci.junction.getPosition(junction_id_from)
                    x_to, y_to = traci.junction.getPosition(junction_id_to)
                    if abs(y_from - y_to) < 1 and abs(x_from - x_to) > 1:
                        lane_pos = abs(stop_x - x_from)
                    elif abs(x_from - x_to) < 1 and abs(y_from - y_to) > 1:
                        lane_pos = abs(stop_y - y_from)

            if lane_pos > lane_length:
                lane_pos = lane_length
            traci.vehicle.setStop(vehID=vehicle_id, edgeID=stop_edge, laneIndex=lane_id, pos=lane_pos, duration=worktime)

        # 添加订阅
        traci.vehicle.subscribe(vehicle_id, [
            tc.VAR_POSITION,
            tc.VAR_ANGLE,
            tc.VAR_SPEED,
            tc.VAR_ROAD_ID,
            tc.VAR_LANE_INDEX,
            tc.VAR_LANE_ID,
            tc.VAR_LANEPOSITION
        ])
        TRUCK_SUBSCRIBED_SET.add(vehicle_id)
        if vehicle_id in TRUCK_UNSUBSCRIBED_SET:
            TRUCK_UNSUBSCRIBED_SET.remove(vehicle_id)

    except Exception as e:
        logger.info(f"设置车辆 {vehicle_id} 的任务时发生错误: {e}")
        logger.info(traceback.format_exc())
        return False


# 重启仿真函数
def restart_simulation(net_file,configs):
    try:
        logger.info("关闭当前仿真会话...")
        traci.close()
        logger.info(f"启动新的仿真会话，使用新路网文件: {net_file}")
        traci.start([configs.sumoBinary, "-c", "dt.sumocfg"])
    except Exception as e:
        logger.error(f"启动仿真时发生错误: {e}")


def clean_temp_files():
    for file in glob.glob("*.rou.xml"):
        os.remove(file)

    for file in glob.glob("net/*.net.xml"):
        if len(os.path.basename(file)) > 16:  #####!!!要初始化源文件,if file != network:
            os.remove(file)
    # 清理 net/net_with_id 文件夹下的 .net.xml 文件
    for file in glob.glob("net/net_with_id/*.net.xml"):
        if len(os.path.basename(file)) > 16:  #####!!!要初始化源文件,if file != network:
            os.remove(file)


def initialize_netxml(vessel_data, configs):
    global ADDED_JUNCTIONS, ADDED_RANGES, VESSEL2LANE
    i = 0
    for vessel, vessel_info in vessel_data.items():
        vessel_id = vessel
        st_x = vessel_info.get("tail_x", None)
        ed_x = vessel_info.get("head_x", None)
        if i != 0:
            net_file_name = netxml.add_netxml(net_file_name, vessel_id, st_x, ed_x, ADDED_JUNCTIONS, ADDED_RANGES,
                                              VESSEL2LANE)
        else:
            net_file_name = netxml.add_netxml(configs.BASE_NET_FILE, vessel_id, st_x, ed_x, ADDED_JUNCTIONS, ADDED_RANGES,
                                              VESSEL2LANE)
        i += 1

    # 创建目标文件夹
    if not os.path.exists('net/net_with_id'):
        os.makedirs('net/net_with_id')

    # 清理中间生成的文件，仅保留最后一个文件，并保留初始文件
    net_file_name = os.path.join(net_file_name)  # 删除中间过程生成的.net.xml文件。
    temp_files = [f for f in os.listdir('net') if
                  f.endswith('.net.xml') and f != os.path.basename(net_file_name) and f != os.path.basename(configs.BASE_NET_FILE)]
    for temp_file in temp_files:
        os.remove(os.path.join('net', temp_file))

    # 复制并重命名最终生成的文件到net_with_id文件夹
    target_filename = os.path.basename(net_file_name).replace('.net.xml', '_id.net.xml')
    target_file_path = os.path.join('net/net_with_id', target_filename)
    shutil.copy(net_file_name, target_file_path)
    return net_file_name


def update_netxml(NET_FILENAME, vessel_data, NET_FILENAME_ID):
    global ADDED_JUNCTIONS
    global ADDED_RANGES
    global VESSEL2LANE
    NET_FILENAME_TEMP = os.path.basename(NET_FILENAME)
    str1 = NET_FILENAME_TEMP.split('.')[0]
    str2 = str1[9:]
    old_vessel_list = str2.split('-')

    old_keys = set(old_vessel_list)
    new_keys = set(vessel_data.keys())

    ##处理更新后船舶数量与原船舶数量一致的情况
    filename_before_added = None
    same_keys = old_keys & new_keys
    deleted_same_number_vessel_id = old_keys - same_keys
    added_same_number_vessel_id = new_keys - same_keys
    added_same_number_vessel_dict = {}
    deleted_same_number_vessel_list = list(deleted_same_number_vessel_id)
    if len(new_keys) == len(old_keys):
        for key in added_same_number_vessel_id:
            added_same_number_vessel_dict[key] = vessel_data[key]

    if len(new_keys) == len(old_keys):
        filename_after_delete = netxml.delete_netxml(NET_FILENAME_ID, deleted_same_number_vessel_list, ADDED_JUNCTIONS, ADDED_RANGES,
                                               VESSEL2LANE)
        filename_after_delete_replace = filename_after_delete.replace("net/net_with_id/", "net/").replace("_id.net.xml", ".net.xml")

        if os.path.exists(filename_after_delete):
            os.makedirs(os.path.dirname(NET_FILENAME), exist_ok=True)
            shutil.copy(filename_after_delete, filename_after_delete_replace)
        else:
            print(f"源文件 {filename_after_delete_replace} 不存在，无法复制。")

        for vessel_id, vessel_info in added_same_number_vessel_dict.items():
            st_x, ed_x = vessel_info.get("tail_x", None), vessel_info.get("head_x", None)
            filename_before_added = netxml.add_netxml(filename_after_delete_replace, vessel_id, st_x, ed_x, ADDED_JUNCTIONS, ADDED_RANGES,
                                                VESSEL2LANE)

    ##处理更新后船舶数量与原船舶数量不一致的情况
    add_vessel_dict = {}
    add_vessels_id = new_keys-old_keys
    delete_vessels_id = old_keys - new_keys
    delete_vessel_list = list(delete_vessels_id)
    if len(add_vessels_id) > 0:
        for key in add_vessels_id:
            add_vessel_dict[key] = vessel_data[key]

    if len(delete_vessel_list) > 0:
        filename_before = netxml.delete_netxml(NET_FILENAME_ID, delete_vessel_list, ADDED_JUNCTIONS, ADDED_RANGES, VESSEL2LANE)
    else:
        filename_before_needed_add = NET_FILENAME
        for vessel_id, vessel_info in add_vessel_dict.items():
            st_x, ed_x = vessel_info.get("tail_x", None), vessel_info.get("head_x", None)
            filename_before = netxml.add_netxml(filename_before_needed_add, vessel_id, st_x, ed_x,  ADDED_JUNCTIONS, ADDED_RANGES, VESSEL2LANE)

    if filename_before_added:
        filename_before = filename_before_added

    return filename_before


def update_vehicle_tasks(need_update_task:dict, need_add_task:dict, simulation_time, configs, NET_FILENAME ):
    global VESSEL2LANE
    global ROUTE_FILE
    global APPEND_STOP_LIST
    global VEHICLE_TO_STOP_COORD
    global ADDED_UPDATED_VEHICLE_ID_EDGE
    global TRUCK_UNSUBSCRIBED_SET
    global TRUCK_START_LIST
    converter = routepy.NetworkConverter(configs.BASE_NET_FILE)

    if len(need_add_task)>0:
        logger.info(f"添加集卡车辆及任务")
        for vehicle_id in need_add_task.keys(): # 添加新车辆
            random_truck_start_list = random.choice(TRUCK_START_LIST)
            x, y = random_truck_start_list
            edge_id, lane_pos = converter.convert_coordinates_to_edge(x, y, radius=20)

            if edge_id is None or lane_pos is None:
                random_truck_start_list = random.choice(TRUCK_START_LIST)
                x, y = random_truck_start_list
                edge_id, lane_pos = converter.convert_coordinates_to_edge(x, y, radius=20)
                if edge_id is None or lane_pos is None:
                    random_truck_start_list = random.choice(TRUCK_START_LIST)
                    x, y = random_truck_start_list
                    edge_id, lane_pos = converter.convert_coordinates_to_edge(x, y, radius=20)

            lane_id = edge_id + "_0"
            lane_length = traci.lane.getLength(lane_id)
            if lane_pos > lane_length:
                lane_pos = lane_length

            ADDED_UPDATED_VEHICLE_ID_EDGE[vehicle_id] = edge_id
            try:
                temp_route_id = f"temp_route_{vehicle_id}_{int(time.time()*1000)}"
                traci.route.add(temp_route_id, [edge_id])
                traci.vehicle.add(vehID=vehicle_id, routeID=temp_route_id, typeID="myType", departPos=lane_pos, departSpeed=0)
                traci.vehicle.setColor(typeID=vehicle_id, color=(255, 0, 0, 255))
                traci.vehicle.setShapeClass(typeID=vehicle_id, shapeClass="truck")
                traci.vehicle.setSpeed(vehicle_id, 0)
                traci.vehicle.setLaneChangeMode(vehicle_id, 0x5ff)

                if vehicle_task_check(vehicle_id, need_add_task[vehicle_id], simulation_time, configs) == True:
                    set_vehicle_route_and_stop(vehicle_id, need_add_task[vehicle_id], configs)
                    traci.vehicle.setSpeed(vehicle_id, -1)
                    logger.info(f"新添加车辆 {vehicle_id} 并设置任务完成。")

            except traci.exceptions.TraCIException as e:
                logger.info(f"添加车辆 {vehicle_id} 失败: {e}。尝试直接设置其作业任务。")
                logger.info(traceback.format_exc())


    if len(need_update_task) > 0:
        logger.info(f"更新集卡车辆任务")
        vehicle_update_positions_coord = {}
        for vehicle_id in need_update_task.keys():
            # #任务更新的车子先取消订阅。
            # if vehicle_id not in TRUCK_UNSUBSCRIBED_SET:
            #     traci.vehicle.unsubscribe(vehicle_id)

            if vehicle_id in VEHICLE_TO_STOP_COORD:
                APPEND_STOP_LIST.append(VEHICLE_TO_STOP_COORD[vehicle_id])
                del VEHICLE_TO_STOP_COORD[vehicle_id]
            try:
                edge_id = traci.vehicle.getRoadID(vehicle_id)
                lane_pos = traci.vehicle.getLanePosition(vehicle_id)

                lane_id = edge_id + "_0"

                try:
                    lane_length = traci.lane.getLength(lane_id)
                except traci.exceptions.TraCIException:
                    traci.vehicle.remove(vehicle_id)

                if lane_pos > lane_length:
                    lane_pos = lane_length

                if edge_id is not None and lane_pos is not None:
                    ADDED_UPDATED_VEHICLE_ID_EDGE[vehicle_id] = edge_id
                    via_from, via_to = routepy.parse_netxml(NET_FILENAME)
                    if ":" in edge_id:
                        if edge_id in via_from and "s1" not in edge_id and "s2" not in edge_id:
                            edge_id = via_from[edge_id]
                            ADDED_UPDATED_VEHICLE_ID_EDGE[vehicle_id] = edge_id
                            if "r" in edge_id and "rest" not in edge_id:
                                lane_pos = 3
                            elif "rest" in edge_id:
                                lane_pos = 2
                        elif "s2" in edge_id and edge_id in via_to:
                            edge_id = via_to[edge_id]
                            ADDED_UPDATED_VEHICLE_ID_EDGE[vehicle_id] = edge_id
                            lane_pos = 1
                        elif "s1" in edge_id and (edge_id in via_to or edge_id in via_from):
                            random_truck_start_list = random.choice(TRUCK_START_LIST)
                            x, y = random_truck_start_list
                            edge_id, lane_pos = converter.convert_coordinates_to_edge(x, y, radius=20)

                            lane_id = edge_id + "_0"
                            # if lane_id is None:
                            #     traci.vehicle.remove(vehicle_id)

                            lane_length = traci.lane.getLength(lane_id)
                            if lane_pos > lane_length:
                                lane_pos = lane_length

                            ADDED_UPDATED_VEHICLE_ID_EDGE[vehicle_id] = edge_id

                    vehicle_update_positions_coord[vehicle_id] = (edge_id, lane_pos)

                elif edge_id is None or lane_pos is None:
                    random_truck_start_list = random.choice(TRUCK_START_LIST)
                    x, y = random_truck_start_list
                    edge_id, lane_pos = converter.convert_coordinates_to_edge(x, y, radius=20)
                    if edge_id is None or lane_pos is None:
                        random_truck_start_list = random.choice(TRUCK_START_LIST)
                        x, y = random_truck_start_list
                        edge_id, lane_pos = converter.convert_coordinates_to_edge(x, y, radius=20)

                    lane_id = edge_id + "_0"
                    # if lane_id is None:
                    #     traci.vehicle.remove(vehicle_id)
                    lane_length = traci.lane.getLength(lane_id)
                    if lane_pos > lane_length:
                        lane_pos = lane_length

                    ADDED_UPDATED_VEHICLE_ID_EDGE[vehicle_id] = edge_id
                    vehicle_update_positions_coord[vehicle_id] = (edge_id, lane_pos)

            except traci.exceptions.TraCIException as e:
                logger.info(f"获取车辆 {vehicle_id} 的位置失败: {e}")
                logger.info(traceback.format_exc())
                return False, f"{vehicle_id}车辆任务更新过早，集卡车未在仿真中加载。"
            try:
                traci.vehicle.remove(vehicle_id)  # 删除当前车子
            except traci.exceptions.TraCIException as e:
                logger.info(f"删除车辆 {vehicle_id} 失败: {e}")
                logger.info(traceback.format_exc())

            edge_id, lane_pos = vehicle_update_positions_coord[vehicle_id]
            try:
                temp_route_id = f"temp_route_{vehicle_id}_{int(time.time()*1000)}"
                traci.route.add(temp_route_id, [edge_id])
                traci.vehicle.add(vehID=vehicle_id, routeID=temp_route_id, typeID="myType", departPos=lane_pos, departSpeed=0)
                traci.vehicle.setColor(typeID=vehicle_id, color=(255, 0, 0, 255))
                traci.vehicle.setShapeClass(typeID=vehicle_id, shapeClass="truck")
                traci.vehicle.setSpeed(vehicle_id, 0)
                traci.vehicle.setLaneChangeMode(vehicle_id, 0x5ff)

                if vehicle_task_check(vehicle_id, need_update_task[vehicle_id], simulation_time, configs) == True:
                    set_vehicle_route_and_stop(vehicle_id, need_update_task[vehicle_id], configs)
                    traci.vehicle.setSpeed(vehicle_id, -1)
                    logger.info(f"集卡车{vehicle_id}任务改变并更新任务完成。")

            except traci.exceptions.TraCIException as e:
                logger.info(f"更新车辆 {vehicle_id} 的任务失败: {e}。尝试直接设置其作业任务。")
                logger.info(traceback.format_exc())


    # 删除没有作业任务的车辆 ——注释的原因是，让该车驶离作业点，防止卡住
    # rest_tasks = last_tasks - current_tasks  # 待优化
    # if len(rest_tasks)>0:
    #     for vehicle_id in rest_tasks:
    #         append_rest_stop_to_vehicle(vehicle_id, ROUTE_FILENAME)


# def initialize_vehicle_tasks(task_data, simulation_time):
#     for vehicle_id, task_info in task_data.items():
#         if vehicle_task_check(vehicle_id, task_data[vehicle_id], simulation_time):
#             set_vehicle_route_and_stop(vehicle_id, task_info)


def process_vehicle_data(vehicle_id, simulation_time, SIMULATION_START, vehicle_task_data, truck_info4update,
                         all_tk_data, subscription_results):
    global TRUCK_UNSUBSCRIBED_SET
    # 从订阅结果中获取数据
    current_pos = subscription_results[vehicle_id][tc.VAR_POSITION]
    task_id = vehicle_task_data.get("task_id", "")
    angle = subscription_results[vehicle_id][tc.VAR_ANGLE]
    speed = subscription_results[vehicle_id][tc.VAR_SPEED]
    road_id = subscription_results[vehicle_id][tc.VAR_ROAD_ID]
    lane_index = subscription_results[vehicle_id][tc.VAR_LANE_INDEX]
    depart_lane = subscription_results[vehicle_id][tc.VAR_LANE_ID]
    depart_pos = subscription_results[vehicle_id][tc.VAR_LANEPOSITION]

    # 获取路线（无法通过订阅获取，仍然需要调用 traci 方法）
    route_edges = " ".join(traci.vehicle.getRoute(vehicle_id))

    # 程序逻辑：stop_exist 默认为 1
    # 在原有作业点上增加一个停车区停车点
    # 获取最后一个停车点，并判断车辆是否到达
    # 到达后，发出最后一次坐标，然后不再发送坐标
    stop_num = len(traci.vehicle.getStops(vehicle_id))
    stop_exist = 1

    if stop_num == 1:
        stops = traci.vehicle.getStops(vehicle_id)
        stop = stops[0]
        current_lane_pos = subscription_results[vehicle_id][tc.VAR_LANEPOSITION]
        current_lane = subscription_results[vehicle_id][tc.VAR_LANE_ID]
        TOLERANCE = 1.5
        if (current_lane == stop.lane and stop.startPos - TOLERANCE <= current_lane_pos <= stop.endPos + TOLERANCE and traci.vehicle.isStopped(vehicle_id)):
            stop_exist = 0

    vehicle_tk_data = {
        "truck_id": vehicle_id,
        "task_id": task_id,
        "simulation_start": SIMULATION_START,
        "simulation_time": simulation_time,
        "current_pos": current_pos,
        "angle": (angle - 90) % 360,
        "stop_exist": stop_exist
    }

    all_tk_data[vehicle_id] = vehicle_tk_data

    truck_info4update[vehicle_id] = {
        "depart_lane": depart_lane,
        "depart_pos": depart_pos,
        "depart_speed": speed,
        "route_edges": road_id
    }
    if stop_exist == 0:
        traci.vehicle.unsubscribe(vehicle_id)
        TRUCK_UNSUBSCRIBED_SET.add(vehicle_id)
        logger.info(f"{vehicle_id}作业完成，状态是{stop_exist}，坐标为：{current_pos}，已取消订阅。")
    # else:
    #     # logger.info(f"{vehicle_id}作业未完成，状态是1，坐标为：{current_pos}")


def run_netconvert(net_filename, configs):
    """
    使用 netconvert 运行并保存路网文件。
    参数:
    net_filename (str): 路网文件的路径。
    返回:
    None
    """
    netconvert_command = [
        configs.netConvert,
        "--sumo-net-file",
        net_filename,
        "-o",
        net_filename,
        "--no-warnings", "true"
    ]
    subprocess.run(netconvert_command, check=True)
    print(f"路网文件已成功保存为: {os.path.basename(net_filename)}")


def simulation_step(NET_FILENAME, SIMULATION_START, NET_FILENAME_ID, configs):
    global VESSEL2LANE, ROUTE_FILENAME, TRUCK_UNSUBSCRIBED_SET, TRUCK_START_LIST, ADDED_UPDATED_VEHICLE_ID_EDGE
    VESSEL2LANE_BEFORE_UPDATE = copy.deepcopy(VESSEL2LANE)
    EXISTED_VESSEL_ID = set(VESSEL2LANE.keys())
    logger.info("开始码头集卡装卸作业路径仿真...")
    i = 0
    j = 0
    LAST_task_data = ''
    while True:
        try:
            start_time = datetime.now()
            target_time = start_time + timedelta(seconds=1)
            start_time = time.time()
            traci.simulationStep()
            simulation_time = traci.simulation.getTime()
            truck_info4update = {}
            all_tk_data = {}
            json_trucks = configs.redis_conn.hgetall("task_data")
            task_data = {key.decode(): json.loads(value) for key, value in json_trucks.items()}

            subscription_results = traci.vehicle.getAllSubscriptionResults()

            for vehicle_id in subscription_results.keys():
                vehicle_task_data = task_data.get(vehicle_id, '')
                omitted_condition1 = not vehicle_task_data
                omitted_condition2 = isinstance(vehicle_task_data, dict) and vehicle_task_data.get("vessel_id") not in EXISTED_VESSEL_ID    #有作业船舶任务的集卡车发送坐标,无作业船舶的集卡车向redis发送一次信息。
                omitted_condition3 = vehicle_id in TRUCK_UNSUBSCRIBED_SET
                if omitted_condition1 or omitted_condition2 or omitted_condition3:
                    continue

                process_vehicle_data(vehicle_id, simulation_time, SIMULATION_START, vehicle_task_data, truck_info4update, all_tk_data, subscription_results)

            tk_data = {vehicle_id: json.dumps(tk_data) for vehicle_id, tk_data in all_tk_data.items()}
            while True:
                current_time = datetime.now()
                if (current_time - target_time).total_seconds() >= -0.005 or current_time >= target_time:
                    break
                time.sleep(0.0001)

            redis_task_data_manager(tk_data, configs, vehicle_id=None)

            json_trucks = configs.redis_conn.hgetall("task_data")
            task_data = {key.decode(): json.loads(value) for key, value in json_trucks.items()}

            if i == 0:
                LAST_task_data = copy.deepcopy(task_data)
                logger.critical(f"标志1：初始化集卡任务！")
                for vehicle_id, task_info in task_data.items():
                    if vehicle_task_check(vehicle_id, task_data[vehicle_id], simulation_time, configs) == True:
                        set_vehicle_route_and_stop(vehicle_id, task_info, configs)
                i += 1
            else:
                cur_sumo_tk_id = traci.vehicle.getLoadedIDList()

                need_add_task = {}
                need_update_task = {}
                for tk_id,task in task_data.items():
                    if tk_id not in cur_sumo_tk_id:
                        need_add_task[tk_id] = task
                    else:
                        old_task = LAST_task_data.get(tk_id)
                        if old_task is None:
                            need_update_task[tk_id] = task
                        else:
                            if task['task_id'] != old_task['task_id']:
                                need_update_task[tk_id] = task

                if len(need_add_task) > 0 or len(need_update_task) > 0:
                    update_vehicle_tasks(need_update_task, need_add_task, simulation_time, configs, NET_FILENAME)
                    LAST_task_data = copy.deepcopy(task_data)

            json_vessel_data = configs.redis_conn.get("vessel_data")
            vessel_data = json.loads(json_vessel_data)
            #把vessel_data中的所有key进行编码。待优化。

            NET_FILENAME_TEMP = os.path.basename(NET_FILENAME)
            str1 = NET_FILENAME_TEMP.split('.')[0]
            str2 = str1[9:]
            if j == 0:
                j += 1
                continue
            else:
                old_vessel_list = str2.split('-')
                old_keys = set(old_vessel_list)
                new_keys = set(vessel_data.keys())

                if (new_keys - old_keys) or (len(old_keys) == len(new_keys) and new_keys != old_keys):
                    NET_FILENAME_BEFORE_UPDATE = NET_FILENAME
                    NET_FILENAME_ID_BEFORE_UPDATE = NET_FILENAME_ID
                    unsubscribe_all_trucks()
                    try:
                        traci.close()
                        NET_FILENAME_UPDATE_TEMP = update_netxml(NET_FILENAME, vessel_data, NET_FILENAME_ID)
                        print(f"添加船舶的路网是{os.path.basename(NET_FILENAME_UPDATE_TEMP)}")
                        if "_id" in os.path.basename(NET_FILENAME_UPDATE_TEMP):
                            NET_FILENAME_ID = NET_FILENAME_UPDATE_TEMP
                            NET_FILENAME_UPDATE = NET_FILENAME_UPDATE_TEMP
                            NET_FILENAME = NET_FILENAME_UPDATE_TEMP.replace("net/net_with_id/", "net/").replace("_id.net.xml", ".net.xml")

                            if os.path.exists(NET_FILENAME_UPDATE_TEMP):
                                os.makedirs(os.path.dirname(NET_FILENAME), exist_ok=True)
                                shutil.copy(NET_FILENAME_UPDATE_TEMP, NET_FILENAME)
                            else:
                                print(f"源文件 {NET_FILENAME_UPDATE_TEMP} 不存在，无法复制。")

                        elif "_id" not in os.path.basename(NET_FILENAME_UPDATE_TEMP):
                            NET_FILENAME = NET_FILENAME_UPDATE_TEMP
                            NET_FILENAME_UPDATE = NET_FILENAME_UPDATE_TEMP
                            NET_FILENAME_ID = NET_FILENAME_UPDATE_TEMP.replace("net/", "net/net_with_id/").replace(".net.xml", "_id.net.xml")

                            if os.path.exists(NET_FILENAME_UPDATE_TEMP):
                                os.makedirs(os.path.dirname(NET_FILENAME), exist_ok=True)
                                shutil.copy(NET_FILENAME_UPDATE_TEMP, NET_FILENAME_ID)
                            else:
                                print(f"源文件 {NET_FILENAME_UPDATE_TEMP} 不存在，无法复制。")

                        logger.warning(f'添加船舶并升级路网为{os.path.basename(NET_FILENAME_UPDATE)}')

                        try:
                            run_netconvert(NET_FILENAME_UPDATE, configs)
                            print(f"路网文件{os.path.basename(NET_FILENAME_UPDATE)}处理成功，继续使用更新后的文件。")
                        except subprocess.CalledProcessError:
                            logger.info(f"处理路网文件{os.path.basename(NET_FILENAME_UPDATE)}时发生错误，使用旧路网文件。")
                            logger.info("当前任务批次的船舶信息如下：")
                            vessel_info = "; ".join(
                                f"{vessel_id}, \"head_x\": {data['head_x']}, \"tail_x\": {data['tail_x']}"
                                for vessel_id, data in vessel_data.items()
                            )
                            logger.info(f"{{{vessel_info}}}")
                            NET_FILENAME = NET_FILENAME_BEFORE_UPDATE
                            NET_FILENAME_ID = NET_FILENAME_ID_BEFORE_UPDATE

                        ROUTE_FILENAME = routepy.update_rouxml_with_netxml(truck_info4update, NET_FILENAME_UPDATE, VESSEL2LANE_BEFORE_UPDATE, VESSEL2LANE, TRUCK_START_LIST, configs)
                        sumoCmd = [
                            configs.sumoBinary,
                            "--net-file", NET_FILENAME_UPDATE,
                            "--route-files", ROUTE_FILENAME,
                            "--gui-settings-file", configs.gui_settings_file,
                            "--time-to-teleport", "15",
                            "--step-length", "1",
                            "--route-steps", "0",
                            "--no-warnings", "true"
                        ]
                        traci.start(sumoCmd)
                        SIMULATION_START = datetime.now().strftime("%Y%m%d-%H:%M:%S")

                        # json_trucks = configs.redis_conn.hgetall("task_data")
                        # task_data = {key.decode(): json.loads(value) for key, value in json_trucks.items()}

                        # logger.critical(f"标志4：重启仿真后初始化集卡任务！")
                        # for vehicle_id, task_info in task_data.items():
                        #     if vehicle_task_check(vehicle_id, task_data[vehicle_id], simulation_time, configs) == True:
                        #         set_vehicle_route_and_stop(vehicle_id, task_info, configs)

                        logger.critical(f"已重启仿真会话，使用新路网文件: {os.path.basename(NET_FILENAME)}")
                    except Exception as e:
                        logger.info(f"启动仿真时发生错误: {e}")

                elif len(old_keys) > len(new_keys):
                    NET_FILENAME_BEFORE_UPDATE = NET_FILENAME
                    NET_FILENAME_ID_BEFORE_UPDATE = NET_FILENAME_ID

                    NET_FILENAME_UPDATE_TEMP = update_netxml(NET_FILENAME, vessel_data, NET_FILENAME_ID)
                    print(f"删除船舶的路网是{os.path.basename(NET_FILENAME_UPDATE_TEMP)}")
                    if "_id" in os.path.basename(NET_FILENAME_UPDATE_TEMP):
                        NET_FILENAME_ID = NET_FILENAME_UPDATE_TEMP
                        NET_FILENAME_UPDATE = NET_FILENAME_UPDATE_TEMP
                        NET_FILENAME = NET_FILENAME_UPDATE_TEMP.replace("net/net_with_id/", "net/").replace("_id.net.xml",".net.xml")

                        if os.path.exists(NET_FILENAME_UPDATE_TEMP):
                            os.makedirs(os.path.dirname(NET_FILENAME), exist_ok=True)
                            shutil.copy(NET_FILENAME_UPDATE_TEMP, NET_FILENAME)
                        else:
                            print(f"源文件 {NET_FILENAME_UPDATE_TEMP} 不存在，无法复制。")

                    elif "_id" not in os.path.basename(NET_FILENAME_UPDATE_TEMP):
                        NET_FILENAME = NET_FILENAME_UPDATE_TEMP
                        NET_FILENAME_UPDATE = NET_FILENAME_UPDATE_TEMP
                        NET_FILENAME_ID = NET_FILENAME_UPDATE_TEMP.replace("net/", "net/net_with_id/").replace(".net.xml","_id.net.xml")

                        if os.path.exists(NET_FILENAME_UPDATE_TEMP):
                            os.makedirs(os.path.dirname(NET_FILENAME), exist_ok=True)
                            shutil.copy(NET_FILENAME_UPDATE_TEMP, NET_FILENAME_ID)
                        else:
                            print(f"源文件 {NET_FILENAME_UPDATE_TEMP} 不存在，无法复制。")

                    logger.warning(f'删除船舶并升级路网为{os.path.basename(NET_FILENAME_UPDATE)}')

                    try:
                        run_netconvert(NET_FILENAME_UPDATE, configs)
                        print(f"路网文件{os.path.basename(NET_FILENAME_UPDATE)}处理成功，继续使用更新后的文件。")
                    except subprocess.CalledProcessError:
                        logger.info(f"处理路网文件{os.path.basename(NET_FILENAME_UPDATE)}时发生错误，使用旧路网文件。")
                        logger.info("当前任务批次的船舶信息如下：")
                        vessel_info = "; ".join(
                            f"{vessel_id}, \"head_x\": {data['head_x']}, \"tail_x\": {data['tail_x']}"
                            for vessel_id, data in vessel_data.items()
                        )
                        logger.info(f"{{{vessel_info}}}")
                        NET_FILENAME = NET_FILENAME_BEFORE_UPDATE
                        NET_FILENAME_ID = NET_FILENAME_ID_BEFORE_UPDATE
                    ROUTE_FILENAME = routepy.update_rouxml_with_netxml(truck_info4update, NET_FILENAME_UPDATE, VESSEL2LANE_BEFORE_UPDATE, VESSEL2LANE, TRUCK_START_LIST, configs)

        except Exception:
            ADDED_UPDATED_VEHICLE_ID_EDGE.clear()
            unsubscribe_all_trucks()
            json_vessel_data = configs.redis_conn.get("vessel_data")
            vessel_data = json.loads(json_vessel_data)
            task_data_json = configs.redis_conn.hgetall("task_data")
            task_data = {key.decode(): json.loads(value) for key, value in task_data_json.items()}

            if len(vessel_data) == 0 or len(task_data) == 0:
                logger.warning("没有货船数据，请等待...")
                time.sleep(10)
                continue

            NET_FILENAME = initialize_netxml(vessel_data, configs)

            try:
                run_netconvert(NET_FILENAME, configs)
                print(f"路网文件{os.path.basename(NET_FILENAME)}处理成功，继续使用更新后的文件。")
            except subprocess.CalledProcessError:
                logger.info(f"处理路网文件{os.path.basename(NET_FILENAME)}时发生错误。")
                logger.info("当前任务批次的船舶信息如下：")
                vessel_info = "; ".join(
                    f"{vessel_id}, \"head_x\": {data['head_x']}, \"tail_x\": {data['tail_x']}"
                    for vessel_id, data in vessel_data.items()
                )
                logger.info(f"{{{vessel_info}}}")

            filename_before = os.path.basename(NET_FILENAME)
            NET_FILENAME_ID = os.path.join("net", "net_with_id", filename_before.replace('.net.xml', '_id.net.xml'))

            if os.path.exists(NET_FILENAME):
                os.makedirs(os.path.dirname(NET_FILENAME), exist_ok=True)
                shutil.copy(NET_FILENAME, NET_FILENAME_ID)
            else:
                print(f"源文件 {NET_FILENAME} 不存在，无法复制。")
            logger.info(f'初始有({len(vessel_data)})货船、({len(task_data)})集卡数据，路网文件初始化成功。')

            ROUTE_FILENAME = routepy.update_rouxml_with_netxml(truck_info4update, NET_FILENAME, VESSEL2LANE_BEFORE_UPDATE, VESSEL2LANE, TRUCK_START_LIST, configs)
            # if task_data:
            #     ROUTE_FILENAME = routepy.initialize_rouxml(configs.BASE_NET_FILE, task_data, TRUCK_START_LIST,
            #                                                configs.BATCH_SIZE, configs.INTERVAL)
            sumoCmd = [
                configs.sumoBinary,
                "--net-file", NET_FILENAME,
                "--route-files", ROUTE_FILENAME,
                "--gui-settings-file", configs.gui_settings_file,
                "--time-to-teleport", "15",
                "--step-length", "1",
                "--route-steps", "0",
                "--no-warnings", "true",
                "--ignore-route-errors", "true"
            ]
            traci.start(sumoCmd)
            SIMULATION_START = datetime.now().strftime("%Y%m%d-%H:%M:%S")

            # json_trucks = configs.redis_conn.hgetall("task_data")
            # task_data = {key.decode(): json.loads(value) for key, value in json_trucks.items()}

            # logger.critical(f"标志4：重启仿真后初始化集卡任务！")
            # for vehicle_id, task_info in task_data.items():
            #     if vehicle_task_check(vehicle_id, task_data[vehicle_id], simulation_time, configs) == True:
            #         set_vehicle_route_and_stop(vehicle_id, task_info, configs)


def run(configs):
    global ROUTE_FILENAME, APPEND_STOP_LIST, TRUCK_START_LIST,ADDED_UPDATED_VEHICLE_ID_EDGE
    while True:
        try:
            ADDED_UPDATED_VEHICLE_ID_EDGE.clear()
            json_vessel_data = configs.redis_conn.get("vessel_data")
            vessel_data = json.loads(json_vessel_data)
            task_data_json = configs.redis_conn.hgetall("task_data")
            task_data = {key.decode(): json.loads(value) for key, value in task_data_json.items()}

            if len(vessel_data) == 0 or len(task_data) == 0:
                logger.warning("没有货船数据，请等待...")
                time.sleep(10)
                continue

            NET_FILENAME = initialize_netxml(vessel_data, configs)
            run_netconvert(NET_FILENAME, configs)
            filename_before = os.path.basename(NET_FILENAME)
            NET_FILENAME_ID = os.path.join("net", "net_with_id", filename_before.replace('.net.xml', '_id.net.xml'))

            if os.path.exists(NET_FILENAME):
                os.makedirs(os.path.dirname(NET_FILENAME), exist_ok=True)
                shutil.copy(NET_FILENAME, NET_FILENAME_ID)
            else:
                print(f"源文件 {NET_FILENAME} 不存在，无法复制。")

            logger.info(f'初始有({len(vessel_data)})货船、({len(task_data)})集卡数据，路网文件初始化成功。')
            ROUTE_FILENAME = ''
            TRUCK_START_LIST = routepy.get_all_car_start_coordinates(configs.BASE_NET_FILE, configs.truck_start_type)
            APPEND_STOP_LIST = routepy.get_all_rest_stop_coordinates(configs.BASE_NET_FILE, configs.truck_stop_type,
                                                                     configs.TRUCK_STOP_TIME)

            if task_data:
                ROUTE_FILENAME = routepy.initialize_rouxml(configs.BASE_NET_FILE, task_data, TRUCK_START_LIST,
                                                           configs.BATCH_SIZE, configs.INTERVAL)
            else:
                logger.info("集卡任务数据为空，无法生成集卡初始位置信息，请检查数据链路！！！！")
                continue

            sumoCmd = [
                configs.sumoBinary,
                "--net-file", NET_FILENAME,
                "--route-files", ROUTE_FILENAME,
                "--gui-settings-file", configs.gui_settings_file,
                "--time-to-teleport", "15",
                "--step-length", "1",
                "--route-steps", "0",
                "--no-warnings", "true",
                "--ignore-route-errors", "true"
            ]
            traci.start(sumoCmd)

            simulation_step(NET_FILENAME, configs.SIMULATION_START, NET_FILENAME_ID, configs)
        except Exception:
            time.sleep(2)
            logger.info("当前仿真异常，该任务批次的船舶信息如下：")
            vessel_info = "; ".join(
                f"{vessel_id}, \"head_x\": {data['head_x']}, \"tail_x\": {data['tail_x']}"
                for vessel_id, data in vessel_data.items()
            )
            logger.info(f"{{{vessel_info}}}")
            continue
        finally:
            try:
                traci.close()
            except:
                logger.warning("关闭 traci 连接时发生错误，忽略并继续...")

